<?php
/**
 * Created by PhpStorm.
 * @author WYZ <wyz@jungo.com.cn>
 * @copyright 深圳市俊网网络有限公司
 */

namespace Selibra\Aspect;


use Selibra\Tools\Console;
use Selibra\Di\DI;

class ProxyObjectGenerator
{

    /**
     * @var string
     */
    protected string $class = '';

    /**
     * @var
     */
    protected static array $created = [];


    /**
     * @param $class
     * @return mixed|object|null
     * @throws \Selibra\Di\Exception\NoImplementClass
     */
    public function get($class)
    {
        $this->class = $class;
        $newClassName = DI::getClassProxyClassName($class);
        if (!in_array($newClassName, self::$created)) {
            $metadata = DI::getContainer()->collector()->get($this->class);
            if ($metadata->getReflectionClass()->isInterface()) {
                $this->createInterfaceProxy($newClassName);
                $metadata->setReflectionClass((new \ReflectionClass($newClassName)));
                DI::getContainer()->collector()->add($this->class, $metadata);
            } else {
                $this->creatClassProxy($newClassName);

            }
        }
        return DI::getContainer()->get($newClassName, DI::getContainer()->collector()->get($this->class));
    }


    /**
     * @param $newClassName
     * @throws \Selibra\Di\Exception\NoImplementClass
     */
    public function creatClassProxy($newClassName)
    {
        $classCode = 'class ' . $newClassName . ' extends \\' . $this->class . PHP_EOL . '{' . PHP_EOL;
        $classCode .= 'private string $class = "' . $this->class . '";' . PHP_EOL;
        $classCode .= $this->methodsCode() . PHP_EOL;
        $classCode .= '}';
        eval($classCode);
        array_push(self::$created, $newClassName);
    }


    /**
     * @param $newClassName
     * @throws \Selibra\Di\Exception\NoImplementClass
     */
    public function createInterfaceProxy($newClassName)
    {
        $classCode = 'class ' . $newClassName . ' implements \\' . $this->class . PHP_EOL . '{' . PHP_EOL;
        $classCode .= $this->methodsCode(true) . PHP_EOL;
        $classCode .= '}';
        eval($classCode);
        array_push(self::$created, $newClassName);
    }


    /**
     * @param $name
     * @param $params
     * @return string
     * @throws \Selibra\Di\Exception\NoImplementClass
     */
    protected function methodsCode($interface = false)
    {
        $metadata = DI::getContainer()->collector()->get($this->class);
        $methods = $metadata->getReflectionClass()->getMethods();
        $methodCode = '';
        foreach ($methods as $method) {
            if (
                !$metadata->getReflectionClass()->isInterface() &&
                (!$method->isPublic() || !AdviceCollector::existAspect($this->class, $method->getName()))
            ) {
                continue;
            }
            $name = $method->getName();
            if (in_array($name, [
                '__construct',
                '__get',
                '__set',
                '__isset',
                '__unset',
                '__call',
                '__autoload',
                '__destruct',
                '__clone',
                '__toString ',
                '__sleep',
                '__wakeup',
                '__set_state',
                '__invoke',
                '__callStatic'
            ])) {
                continue;
            }
            $returnType = $method->getReturnType();
            $reflectionParameters = $method->getParameters();
            $paramCode = '';
            $arguments = '';
            foreach ($reflectionParameters as $param) {
                $paramCode .= $param->getType() . ' $' . $param->getName();

                if ($method->getName() == 'addArgument' && $param->getName() == 'mode') {
                    var_dump($param->allowsNull());
                }
                if ($param->allowsNull()) {
                    $paramCode .= ' = null';
                } else {
                    try {
                        $defaultValue = $param->getDefaultValue();
                        if ($param->getType() . ' -' == 'bool -') {
                            $paramCode .= ' = ' . ($defaultValue ? 'true' : 'false');
                        } elseif (empty($defaultValue)) {
                            $paramCode .= ' = \'\'';
                        } else {
                            $paramCode .= ' = ' . $defaultValue;
                        }
                    } catch (\ReflectionException $exception) {
                    }
                }
                $arguments .= '$' . $param->getName() . ',';
                $paramCode .= ',';
            }
            $paramCode = substr($paramCode, 0, -1);
            $arguments = substr($arguments, 0, -1);
            if ($method->isStatic()) {
                $methodCode .= ' static ';
            }
            if (empty($returnType . '')) {
                $methodCode .= 'function ' . $name . '(' . $paramCode . '){' . PHP_EOL;
            } else {
                $methodCode .= 'function ' . $name . '(' . $paramCode . '): ' . $returnType . PHP_EOL;
                $methodCode .= '{' . PHP_EOL;
            }
            $methodCode .= '$aspectCallbackContext = new \\Selibra\\Aspect\\Context\\AspectCallbackContext(func_get_args());';
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_BEFORE . '",$aspectCallbackContext);' . PHP_EOL;
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_AROUND . '",$aspectCallbackContext);' . PHP_EOL;
            $methodCode .= 'try{' . PHP_EOL;
            if ($interface) {
                $methodCode .= '$interfaceMethodTrigger = new \\Selibra\\Aspect\\InterfaceMethodTrigger("' . $this->class . '","' . $name . '");';
                $methodCode .= '$result = $interfaceMethodTrigger->run(' . $arguments . ');' . PHP_EOL;
            } else {
                $methodCode .= '$result = parent::' . $name . '(' . $arguments . ');' . PHP_EOL;
            }
            $methodCode .= '}catch(\Throwable $throwable){' . PHP_EOL;
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_AFTER_THROW . '",$aspectCallbackContext->setThrowAble($throwable));' . PHP_EOL;
            $methodCode .= 'throw $throwable;' . PHP_EOL;
            $methodCode .= '}' . PHP_EOL;
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_AROUND . '",$aspectCallbackContext);' . PHP_EOL;
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_AFTER_RETURN . '",$aspectCallbackContext->setFunctionReturnValue($result));' . PHP_EOL;
            $methodCode .= '\\Selibra\\Aspect\\Advice::run("' . $this->class . '","' . $name . '","' . AspectConstants::TYPE_AFTER . '",$aspectCallbackContext);' . PHP_EOL;
            if ($returnType != 'void') {
                $methodCode .= 'return $result;' . PHP_EOL;
            }
            $methodCode .= '}' . PHP_EOL;
        }
        return $methodCode;
    }

}
